{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a461c72d",
   "metadata": {
    "id": "a461c72d"
   },
   "source": [
    "# OptiGuide Example with Pyomo\n",
    "\n",
    "\n",
    "Authors: [Phuc Van Phan](https://github.com/pphuc25), [Alfredo Hernandez](https://github.com/alfredoihernandez), [Beibin Li](https://github.com/beibinli)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59a05fc7",
   "metadata": {
    "id": "59a05fc7"
   },
   "source": [
    "For this notebook, we mainly introduce how to use Pyomo with OptiGuide, and we skipped the overview of OptiGuide what-if analysis. If you haven't checked the [OptiGuide example with Gurobi](optiguide_example.ipynb), please check it first.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ReAgLnDma3oI",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ReAgLnDma3oI",
    "outputId": "c95a23e8-ddb9-4fd2-f218-c40a51cbcbb0"
   },
   "outputs": [],
   "source": [
    "# Install Required Packages\n",
    "# %pip install optiguide"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9a3b79c4",
   "metadata": {
    "id": "9a3b79c4"
   },
   "outputs": [],
   "source": [
    "# test Gurobi installation\n",
    "import gurobipy as gp\n",
    "from gurobipy import GRB\n",
    "from eventlet.timeout import Timeout\n",
    "\n",
    "# import auxillary packages\n",
    "import requests  # for loading the example source code\n",
    "import openai\n",
    "\n",
    "# import autogen\n",
    "import autogen\n",
    "from autogen.agentchat import Agent, UserProxyAgent\n",
    "from optiguide import OptiGuideAgent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "aedf19e7",
   "metadata": {
    "id": "aedf19e7"
   },
   "outputs": [],
   "source": [
    "config_list = autogen.config_list_from_json(\n",
    "    \"OAI_CONFIG_LIST\",\n",
    "    filter_dict={\n",
    "        \"model\": {\n",
    "            \"gpt-4\",\n",
    "        }\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9e7e728",
   "metadata": {
    "id": "e9e7e728"
   },
   "source": [
    "Now, let's import the source code (loading from URL) and also some training examples (defined as string blow)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ca962ac5",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ca962ac5",
    "outputId": "4a789991-8ba1-46f3-aad5-7fbaaaff7f02"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "import time\n",
      "\n",
      "from pyomo.environ import (Binary, ConcreteModel, Constraint, Integers,\n",
      "                           Objective, SolverFactory, SolverStatus,\n",
      "                           TerminationCondition, Var, minimize)\n",
      "\n",
      "# Example data\n",
      "capacity_in_supplier = {'supplier1': 150, 'supplier2': 50, 'supplier3': 100}\n",
      "shipping_cost_from_supplier_to_roastery = {\n",
      "    ('supplier1', 'roastery1'): 5,\n",
      ".\n",
      ".\n",
      ".\n",
      "\n",
      "# You can change the solver as per your requirement\n",
      "m = solver.solve(model)\n",
      "\n",
      "print(time.ctime())\n",
      "if m.solver.termination_condition == TerminationCondition.optimal:\n",
      "    print(f'Optimal cost: {model.obj()}')\n",
      "else:\n",
      "    print(\"Not solved to optimality. Optimization status:\", m.solver.termination_condition)\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Get the source code of our coffee example\n",
    "code_url = \"https://raw.githubusercontent.com/microsoft/OptiGuide/main/benchmark/application/coffee_pyomo.py\"\n",
    "response  = requests.get(code_url)\n",
    "\n",
    "\n",
    "# Check if the request was successful\n",
    "if response.status_code == 200:\n",
    "    # Get the text content from the response\n",
    "    code = response.text\n",
    "else:\n",
    "    raise RuntimeError(\"Failed to retrieve the file.\")\n",
    "# code = open(\"../benchmark/application/coffee_pyomo.py\", \"r\").read() # for local files\n",
    "\n",
    "\n",
    "# show the first head and tail of the source code\n",
    "print(\"\\n\".join(code.split(\"\\n\")[:10]))\n",
    "print(\".\\n\" * 3)\n",
    "print(\"\\n\".join(code.split(\"\\n\")[-10:]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e31c4b36",
   "metadata": {
    "code_folding": [],
    "id": "e31c4b36"
   },
   "outputs": [],
   "source": [
    "# In-context learning examples.\n",
    "pyomo_example_qa = \"\"\"\n",
    "----------\n",
    "Question: Why is it not recommended to use just one supplier for roastery 2?\n",
    "Answer Code:\n",
    "```python\n",
    "m.z = Var(suppliers, domain=Binary, bounds=(0, None))\n",
    "def one_supplier_constraint(m):\n",
    "    return sum(m.z[s] for s in suppliers) <= 1\n",
    "m.OneSupplierConstraint = Constraint(rule=one_supplier_constraint)\n",
    "def roastery_2_demand_met_constraint(m, s):\n",
    "    return m.x[s,'roastery2'] <= capacity_in_supplier[s] * m.z[s]\n",
    "m.Roastery2DemandMetConstraint = Constraint(suppliers, rule=roastery_2_demand_met_constraint)\n",
    "```\n",
    "\n",
    "----------\n",
    "Question: What if there's a 13% jump in the demand for light coffee at cafe1?\n",
    "Answer Code:\n",
    "```python\n",
    "light_coffee_needed_for_cafe[\"cafe1\"] = light_coffee_needed_for_cafe[\"cafe1\"] * (1 + 13/100)\n",
    "```\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a5a7d7e",
   "metadata": {
    "id": "5a5a7d7e"
   },
   "source": [
    "Now, let's create an OptiGuide agent and also a user.\n",
    "\n",
    "For the OptiGuide agent, we only allow \"debug_times\" to be 1, which means it can debug its answer once if it encountered errors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "af53727c",
   "metadata": {
    "id": "af53727c"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fri Jan 19 10:30:43 2024\n",
      "Optimal cost: 2470.0\n"
     ]
    }
   ],
   "source": [
    "agent = OptiGuideAgent(\n",
    "    name=\"OptiGuide Coffee Example\",\n",
    "    source_code=code,\n",
    "    debug_times=5,\n",
    "    example_qa=pyomo_example_qa,\n",
    "    solver_software=\"pyomo\",\n",
    "    use_safeguard=True,\n",
    "    llm_config={\n",
    "        \"seed\": 42,\n",
    "        \"config_list\": config_list,\n",
    "    }\n",
    ")\n",
    "\n",
    "user = UserProxyAgent(\n",
    "    \"user\", max_consecutive_auto_reply=0,\n",
    "    human_input_mode=\"NEVER\", code_execution_config=False\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd615e87",
   "metadata": {
    "id": "bd615e87"
   },
   "source": [
    "Now, let's create a user's question."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "24a76f67",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "24a76f67",
    "outputId": "2399e32e-fe3f-429f-9c40-fcc75605741d",
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33muser\u001b[0m (to OptiGuide Coffee Example):\n",
      "\n",
      "Why is it not recommended to use just one supplier for roastery 2?\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mOptiGuide Coffee Example\u001b[0m (to writer):\n",
      "\n",
      "\n",
      "Answer Code:\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mwriter\u001b[0m (to OptiGuide Coffee Example):\n",
      "\n",
      "```python\n",
      "# Adding constraint to ensure roastery 2 does not use just one supplier\n",
      "m.z = Var(suppliers, domain=Binary)\n",
      "def one_supplier_constraint(m):\n",
      "    return sum(m.z[s] for s in suppliers) <= 1\n",
      "m.OneSupplierConstraint = Constraint(rule=one_supplier_constraint)\n",
      "\n",
      "def roastery_2_demand_met_constraint(m, s):\n",
      "    return m.x[s, 'roastery2'] <= capacity_in_supplier[s] * m.z[s]\n",
      "m.Roastery2DemandMetConstraint = Constraint(suppliers, rule=roastery_2_demand_met_constraint)\n",
      "```\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mOptiGuide Coffee Example\u001b[0m (to safeguard):\n",
      "\n",
      "\n",
      "--- Code ---\n",
      "# Adding constraint to ensure roastery 2 does not use just one supplier\n",
      "m.z = Var(suppliers, domain=Binary)\n",
      "def one_supplier_constraint(m):\n",
      "    return sum(m.z[s] for s in suppliers) <= 1\n",
      "m.OneSupplierConstraint = Constraint(rule=one_supplier_constraint)\n",
      "\n",
      "def roastery_2_demand_met_constraint(m, s):\n",
      "    return m.x[s, 'roastery2'] <= capacity_in_supplier[s] * m.z[s]\n",
      "m.Roastery2DemandMetConstraint = Constraint(suppliers, rule=roastery_2_demand_met_constraint)\n",
      "\n",
      "--- One-Word Answer: SAFE or DANGER ---\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33msafeguard\u001b[0m (to OptiGuide Coffee Example):\n",
      "\n",
      "SAFE\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "Fri Jan 19 10:31:28 2024\n",
      "Optimal cost: 2520.0\n",
      "\u001b[33mOptimization problem solved. The objective value is: 2520.0\u001b[0m\n",
      "\u001b[33mOptiGuide Coffee Example\u001b[0m (to writer):\n",
      "\n",
      "Here are the execution results: Optimization problem solved. The objective value is: 2520.0\n",
      "\n",
      "Can you organize these information to a human readable answer?\n",
      "Remember to compare the new results to the original results you obtained in the\n",
      "beginning.\n",
      "\n",
      "--- HUMAN READABLE ANSWER ---\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mwriter\u001b[0m (to OptiGuide Coffee Example):\n",
      "\n",
      "Using only one supplier for Roastery 2 has led to a suboptimal configuration when compared to the previous setup. Initially, the optimal supply chain cost was 2470.0. After implementing the constraint that restricts Roastery 2 to a single supplier, the optimal cost increased to 2520.0. This indicates that depending solely on one supplier for Roastery 2 incurs an additional cost of 50.0, thereby making it less economical.\n",
      "\n",
      "These results emphasize the value of diversifying suppliers for Roastery 2 to minimize overall costs, as relying on a single supplier reduces flexibility and increases the susceptibility to supply chain risks, which can translate into higher costs.\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mOptiGuide Coffee Example\u001b[0m (to user):\n",
      "\n",
      "Using only one supplier for Roastery 2 has led to a suboptimal configuration when compared to the previous setup. Initially, the optimal supply chain cost was 2470.0. After implementing the constraint that restricts Roastery 2 to a single supplier, the optimal cost increased to 2520.0. This indicates that depending solely on one supplier for Roastery 2 incurs an additional cost of 50.0, thereby making it less economical.\n",
      "\n",
      "These results emphasize the value of diversifying suppliers for Roastery 2 to minimize overall costs, as relying on a single supplier reduces flexibility and increases the susceptibility to supply chain risks, which can translate into higher costs.\n",
      "\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# user.initiate_chat(agent, message=\"What if we prohibit shipping from supplier 1 to roastery 2?\")\n",
    "user.initiate_chat(agent, message=\"Why is it not recommended to use just one supplier for roastery 2?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "cbdd1f28",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "cbdd1f28",
    "outputId": "c5e943cf-fce7-4484-8cd8-433d4fede8e5",
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33muser\u001b[0m (to OptiGuide Coffee Example):\n",
      "\n",
      "What is the impact of supplier1 being able to supply only half the quantity at present?\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mOptiGuide Coffee Example\u001b[0m (to writer):\n",
      "\n",
      "\n",
      "Answer Code:\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mwriter\u001b[0m (to OptiGuide Coffee Example):\n",
      "\n",
      "```python\n",
      "# Reducing the capacity of supplier1 to half of its current value\n",
      "capacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] / 2\n",
      "```\n",
      "This snippet modifies the capacity of supplier1 in the `capacity_in_supplier` dictionary by halving its current value before the constraints are added to the model that uses the capacities. It will directly influence the supply constraint for supplier1 in the optimization model.\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mOptiGuide Coffee Example\u001b[0m (to safeguard):\n",
      "\n",
      "\n",
      "--- Code ---\n",
      "# Reducing the capacity of supplier1 to half of its current value\n",
      "capacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] / 2\n",
      "\n",
      "--- One-Word Answer: SAFE or DANGER ---\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33msafeguard\u001b[0m (to OptiGuide Coffee Example):\n",
      "\n",
      "SAFE\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "Fri Jan 19 10:31:55 2024\n",
      "Not solved to optimality. Optimization status: infeasible\n",
      "\u001b[33minfeasible\u001b[0m\n",
      "\u001b[33mOptiGuide Coffee Example\u001b[0m (to writer):\n",
      "\n",
      "Here are the execution results: infeasible\n",
      "\n",
      "Can you organize these information to a human readable answer?\n",
      "Remember to compare the new results to the original results you obtained in the\n",
      "beginning.\n",
      "\n",
      "--- HUMAN READABLE ANSWER ---\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mwriter\u001b[0m (to OptiGuide Coffee Example):\n",
      "\n",
      "Certainly! Here's a human-readable explanation of the impact on the supply chain when supplier1 can only supply half the quantity:\n",
      "\n",
      "Before the change, the optimization problem was solved, with an optimal cost determined to be 2470.0. This result suggested that under the previous conditions, the supply chain was able to meet all the demands of the cafes for both light and dark coffee at the minimum cost.\n",
      "\n",
      "After reducing the capacity of supplier1 by half, the optimization model could not find a feasible solution. The status 'infeasible' indicates that the reduced capacity of supplier1 led to a situation where the supply constraints of the optimization model could not be satisfied. In other words, there was not enough supply to meet the demand for coffee at the cafes given the current distribution and capacity configuration.\n",
      "\n",
      "The original result showed that the supply chain operated efficiently, while the new result after the change indicates a disruption. Supplier1's reduced capacity has created a bottleneck in the supply chain that cannot be compensated by the current setup, resulting in an inability to meet all the demands. This highlights the risk of relying too heavily on a single supplier and the potential need for contingency planning or capacity adjustments among other suppliers to maintain the supply chain's reliability and efficiency.\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mOptiGuide Coffee Example\u001b[0m (to user):\n",
      "\n",
      "Certainly! Here's a human-readable explanation of the impact on the supply chain when supplier1 can only supply half the quantity:\n",
      "\n",
      "Before the change, the optimization problem was solved, with an optimal cost determined to be 2470.0. This result suggested that under the previous conditions, the supply chain was able to meet all the demands of the cafes for both light and dark coffee at the minimum cost.\n",
      "\n",
      "After reducing the capacity of supplier1 by half, the optimization model could not find a feasible solution. The status 'infeasible' indicates that the reduced capacity of supplier1 led to a situation where the supply constraints of the optimization model could not be satisfied. In other words, there was not enough supply to meet the demand for coffee at the cafes given the current distribution and capacity configuration.\n",
      "\n",
      "The original result showed that the supply chain operated efficiently, while the new result after the change indicates a disruption. Supplier1's reduced capacity has created a bottleneck in the supply chain that cannot be compensated by the current setup, resulting in an inability to meet all the demands. This highlights the risk of relying too heavily on a single supplier and the potential need for contingency planning or capacity adjustments among other suppliers to maintain the supply chain's reliability and efficiency.\n",
      "\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "user.initiate_chat(agent, message=\"What is the impact of supplier1 being able to supply only half the quantity at present?\")"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.16"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
